/******************************************************************************
*                                                                             *
* UTICA softcore v0.1                                                         *
*                                                                             *
* Copyright (c) 2012 Andrew D. Zonenberg                                      *
* All rights reserved.                                                        *
*                                                                             *
* Redistribution and use in source and binary forms, with or without modifi-  *
* cation, are permitted provided that the following conditions are met:       *
*                                                                             *
*    * Redistributions of source code must retain the above copyright notice  *
*      this list of conditions and the following disclaimer.                  *
*                                                                             *
*    * Redistributions in binary form must reproduce the above copyright      *
*      notice, this list of conditions and the following disclaimer in the    *
*      documentation and/or other materials provided with the distribution.   *
*                                                                             *
*    * Neither the name of the author nor the names of any contributors may be*
*      used to endorse or promote products derived from this software without *
*      specific prior written permission.                                     *
*                                                                             *
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS OR IMPLIED *
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF        *
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN     *
* NO EVENT SHALL THE AUTHORS BE HELD LIABLE FOR ANY DIRECT, INDIRECT,         *
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT    *
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,   *
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY       *
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT         *
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF    *
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.           *
*                                                                             *
******************************************************************************/

/**
	@file MainWindow.cpp
	@author Andrew D. Zonenberg
	@brief Main application window
 */

#include "MainWindow.h"
#include <gtkmm/messagedialog.h>
#include <gtkmm/stock.h>
#include <iostream>

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <termios.h>
#include <memory.h>

#include <stdint.h>

using namespace std;

void* UartThreadProc(void* pWnd);

MainWindow::MainWindow(std::string tty)
 : m_vgraph(this)
 , m_igraph(this)
 , m_pgraph(this)
 , m_tgraph(this)
 , m_3v3("3V3")
 , m_2v5("2V5")
 , m_1v2("1V2")
  , m_tpsu("Tpsu")
  , m_tamb("Tamb")
{
	pthread_mutex_init(&m_mutex, NULL);
	
	m_ttyfname = tty;
	
	//Initial setup
	set_title("Board Health Monitor");
	set_reallocate_redraws(true);
	set_default_size(1024, 768);
	
	//Set our timer
	sigc::slot<bool> slot = sigc::bind(sigc::mem_fun(*this, &MainWindow::OnTimer), 1);
	sigc::connection conn = Glib::signal_timeout().connect(slot, 500);	

	try
	{
		//Add widgets
		CreateWidgets();
	}
	catch(std::string err)
	{
		cerr << err.c_str();
		exit(-1);
	}
	
	//Done adding widgets
	show_all();
	
}

MainWindow::~MainWindow()
{

}

void MainWindow::CreateWidgets()
{	
	const int label_width = 12;
	
	add(m_vbox);
		m_vbox.pack_start(m_vframe);
			m_vframe.set_label("Voltage");
				m_vframe.add(m_vhbox);
					m_vhbox.pack_start(m_vgraph);
					m_vhbox.pack_end(m_vvbox, Gtk::PACK_SHRINK);
						m_vvbox.pack_start(m_v33label);
						m_vvbox.pack_start(m_v25label);
						m_vvbox.pack_start(m_v12label);
						m_v33label.set_width_chars(label_width);
						m_v25label.set_width_chars(label_width);
						m_v12label.set_width_chars(label_width);
		m_vbox.pack_start(m_iframe);
			m_iframe.set_label("Current");
				m_iframe.add(m_ihbox);
					m_ihbox.pack_start(m_igraph);
					m_ihbox.pack_end(m_ivbox, Gtk::PACK_SHRINK);
						m_ivbox.pack_start(m_i33label);
						m_ivbox.pack_start(m_i25label);
						m_ivbox.pack_start(m_i12label);
						m_i33label.set_width_chars(label_width);
						m_i25label.set_width_chars(label_width);
						m_i12label.set_width_chars(label_width);
		m_vbox.pack_start(m_pframe);
			m_pframe.set_label("Power");
				m_pframe.add(m_phbox);
					m_phbox.pack_start(m_pgraph);
					m_phbox.pack_end(m_pvbox, Gtk::PACK_SHRINK);
						m_pvbox.pack_start(m_p33label);
						m_pvbox.pack_start(m_p25label);
						m_pvbox.pack_start(m_p12label);
						m_p33label.set_width_chars(label_width);
						m_p25label.set_width_chars(label_width);
						m_p12label.set_width_chars(label_width);
		m_vbox.pack_start(m_pframe);
		m_vbox.pack_start(m_tframe);
			m_tframe.set_label("Temperature");
				m_tframe.add(m_thbox);
					m_thbox.pack_start(m_tgraph);
					m_thbox.pack_end(m_tvbox, Gtk::PACK_SHRINK);
						m_tvbox.pack_start(m_tpsulabel);
						m_tvbox.pack_start(m_tamblabel);
						m_tpsulabel.set_width_chars(label_width);
						m_tamblabel.set_width_chars(label_width);
	
	//Set up scales for graphs
	m_vgraph.m_units = "mV";
	m_vgraph.m_minScale = 0;
	m_vgraph.m_maxScale = 4000;
	m_vgraph.m_scaleBump = 1000;
	
	m_igraph.m_units = "mA";
	m_igraph.m_minScale = 0;
	m_igraph.m_maxScale = 1000;
	m_igraph.m_scaleBump = 250;
	
	m_pgraph.m_units = "mW";
	m_pgraph.m_minScale = 0;
	m_pgraph.m_maxScale = 2500;
	m_pgraph.m_scaleBump = 500;
	
	m_tgraph.m_units = "C";
	m_tgraph.m_minScale = 0;
	m_tgraph.m_maxScale = 100;
	m_tgraph.m_scaleBump = 20;
	
	//Add data series to graphs
	m_vgraph.m_seriesName = "voltage";
	m_vgraph.m_series.push_back(&m_3v3);
	m_vgraph.m_series.push_back(&m_2v5);
	m_vgraph.m_series.push_back(&m_1v2);
	
	m_igraph.m_seriesName = "current";
	m_igraph.m_series.push_back(&m_3v3);
	m_igraph.m_series.push_back(&m_2v5);
	m_igraph.m_series.push_back(&m_1v2);
	
	m_pgraph.m_seriesName = "power";
	m_pgraph.m_series.push_back(&m_3v3);
	m_pgraph.m_series.push_back(&m_2v5);
	m_pgraph.m_series.push_back(&m_1v2);
	
	m_tgraph.m_seriesName = "temperature";
	m_tgraph.m_series.push_back(&m_tpsu);
	m_tgraph.m_series.push_back(&m_tamb);
	
	//Set up colors on graphs
	m_3v3.m_color.set_rgb_p(0.0f, 0.5f, 0.0f);
	m_2v5.m_color.set_rgb_p(0.0f, 0.0f, 1.0f);
	m_1v2.m_color.set_rgb_p(0.5f, 0.0f, 0.0f);
	m_tpsu.m_color.set_rgb_p(0.5f, 0.0f, 0.0f);
	m_tamb.m_color.set_rgb_p(0.0f, 0.0f, 1.0f);
	
	pthread_t thread;
	pthread_create(&thread, NULL, UartThreadProc, this);
		
	//Set up viewport
	show_all();
}

double GetTime()
{
	timespec t;
	clock_gettime(CLOCK_REALTIME,&t);
	double d = static_cast<double>(t.tv_nsec) / 1E9f;
	d += t.tv_sec;
	return d;
}

void* UartThreadProc(void* p)
{
	MainWindow* pWnd = (MainWindow*)p;
	
	//Connect to the UART
	int hfile = open(pWnd->m_ttyfname.c_str(), O_RDWR);
	if(hfile < 0)
	{
		perror("couldn't open uart");
		return NULL;
	}
	
	//Set flags
	termios flags;
	memset(&flags, 0, sizeof(flags));
	tcgetattr(hfile, &flags);
	flags.c_cflag = B115200 | CS8 | CLOCAL | CREAD;
	flags.c_iflag = 0;
	flags.c_cc[VMIN] = 1;
	if(0 != tcflush(hfile, TCIFLUSH))
	{
		perror("fail to flush tty");
		return NULL;
	}
	if(0 != tcsetattr(hfile, TCSANOW, &flags))
	{
		perror("fail to set attr");
		return NULL;
	}
	
	//Start reading data words
	bool locked = false;
	
	unsigned short frames[16];
	
	const unsigned int header_length = 10;
	unsigned char caldata[64];
	unsigned char header_sequence[header_length]=
	{
		0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x55, 0x55,
	};
	while(true)
	{
		//Search for sync header
		if(!locked)
		{	
			//read 64 bytes (guaranteed to get at least one full frame since each one is 32 bytes)
			if(64 != pWnd->read_looped(hfile, caldata, 64))
			{
				perror("fail to read data\n");
				return NULL;
			}

			//Look for the header sequence: 0x1234 5678 9abc def0 5555
			unsigned char* pHeader = (unsigned char*)memmem(caldata, 32, header_sequence, header_length);
			if(pHeader == NULL)
			{
				printf("Couldn't find header, trying again...\n");
				continue;
			}
			
			//Once we found the header, find the start of the next frame
			int offset = pHeader - caldata;
			printf("Found header at offset %d\n", offset);		
			int startOfNext = offset + 64;
			printf("Next sync sequence starts at offset %d\n", startOfNext);
			int bytesToRead = startOfNext - 64;
			printf("Need to read %d more bytes to lock on\n", bytesToRead);
			if(bytesToRead != pWnd->read_looped(hfile, caldata, bytesToRead))
			{
				perror("fail to read data\n");
				return NULL;
			}
			
			//Verify that we're locked on
			if(32 != pWnd->read_looped(hfile, caldata, 32))
			{
				perror("fail to read data\n");
				return NULL;
			}
			if(0 == memcmp(caldata, header_sequence, header_length))
			{
				locked = true;
				printf("Locked on\n");
			}
			continue;
		}
		
		//Read a 32-byte data frame
		if(32 != pWnd->read_looped(hfile, (unsigned char*)frames, 32))
		{
			perror("fail to read data\n");
			return NULL;
		}
		
		//Byte swap
		for(int i=0; i<16; i++)
		{
			int hi = frames[i] >> 8;
			int lo = frames[i] & 0xff;
			frames[i] = (lo << 8) | hi;
		}
		
		double now = GetTime();
		
		pWnd->t_amb = frames[15];
		pWnd->t_psu = frames[5];
		
		pWnd->v_33 = frames[6];
		pWnd->v_25 = frames[7];
		pWnd->v_12 = frames[8];
		
		pWnd->i_33 = frames[9];
		pWnd->i_25 = frames[10];
		pWnd->i_12 = frames[11];
		
		pWnd->p_33 = frames[12];
		pWnd->p_25 = frames[13];
		pWnd->p_12 = frames[14];
		
		//Unit conversions
		float v_scale = 1.25;		//mV / LSB
		float i_scale = 0.1;		//mA / LSB
		float p_scale = 2.5;		//mW / LSB
		float t_scale = 0.00390625;	//C / LSB
		
		pWnd->v_33 *= v_scale;
		pWnd->v_25 *= v_scale;
		pWnd->v_12 *= v_scale;
		
		pWnd->i_33 *= i_scale;
		pWnd->i_25 *= i_scale;
		pWnd->i_12 *= i_scale;
		
		pWnd->p_33 *= p_scale;
		pWnd->p_25 *= p_scale;
		pWnd->p_12 *= p_scale;
		
		pWnd->t_amb *= t_scale;
		pWnd->t_psu *= t_scale;
		
		pWnd->m_3v3.GetSeries("voltage")->push_back(GraphPoint(now, pWnd->v_33));
		pWnd->m_3v3.GetSeries("current")->push_back(GraphPoint(now, pWnd->i_33));
		pWnd->m_3v3.GetSeries("power")->push_back(GraphPoint(now, pWnd->p_33));
		
		pWnd->m_2v5.GetSeries("voltage")->push_back(GraphPoint(now, pWnd->v_25));
		pWnd->m_2v5.GetSeries("current")->push_back(GraphPoint(now, pWnd->i_25));
		pWnd->m_2v5.GetSeries("power")->push_back(GraphPoint(now, pWnd->p_25));
		
		pWnd->m_1v2.GetSeries("voltage")->push_back(GraphPoint(now, pWnd->v_12));
		pWnd->m_1v2.GetSeries("current")->push_back(GraphPoint(now, pWnd->i_12));
		pWnd->m_1v2.GetSeries("power")->push_back(GraphPoint(now, pWnd->p_12));
		
		pWnd->m_tamb.GetSeries("temperature")->push_back(GraphPoint(now, pWnd->t_amb));
		pWnd->m_tpsu.GetSeries("temperature")->push_back(GraphPoint(now, pWnd->t_psu));
	}
	
	close(hfile);
	
	return NULL;
}

int MainWindow::read_looped(int fd, unsigned char* buf, int count)
{
	unsigned char* p = buf;
	int bytes_left = count;
	int x = 0;
	while( (x = read(fd, p, bytes_left)) > 0)
	{
		if(x < 0)
		{
			perror("fail to read");
			return -1;
		}
		bytes_left -= x;
		p += x;
	}
	
	return count;
}

bool MainWindow::OnTimer(int nTimer)
{
	if(nTimer == 1)
	{
		//Update text
		char buf[32];
		
		snprintf(buf, 31, "3V3: %.0f mV", v_33);
		m_v33label.set_text(buf);
		snprintf(buf, 31, "2V5: %.0f mV", v_25);
		m_v25label.set_text(buf);
		snprintf(buf, 31, "1V2: %.0f mV", v_12);
		m_v12label.set_text(buf);
		
		snprintf(buf, 31, "3V3: %.0f mA", i_33);
		m_i33label.set_text(buf);
		snprintf(buf, 31, "2V5: %.0f mA", i_25);
		m_i25label.set_text(buf);
		snprintf(buf, 31, "1V2: %.0f mA", i_12);
		m_i12label.set_text(buf);
		
		snprintf(buf, 31, "3V3: %.0f mW", p_33);
		m_p33label.set_text(buf);
		snprintf(buf, 31, "2V5: %.0f mW", p_25);
		m_p25label.set_text(buf);
		snprintf(buf, 31, "1V2: %.0f mW", p_12);
		m_p12label.set_text(buf);
		
		snprintf(buf, 31, "Tpsu: %.2f C", t_psu);
		m_tpsulabel.set_text(buf);
		
		snprintf(buf, 31, "Tamb: %.2f C", t_amb);
		m_tamblabel.set_text(buf);
	}
	
	//false to stop timer
	return true;
}
